// DataBinder.ts
import { Component, _decorator } from 'cc';
const { ccclass } = _decorator;

export type BindingInfo = {
    node: Component; // 目标组件（cc.Label, cc.EditBox…）
    viewProp: string; // 绑定到 View 的属性名（e.g. 'string'、'text'）
    eventName?: string; // （可选）双向绑定事件名（e.g. 'text-changed'）
    convertToView?: (v: any) => any; // 值从 Model → View 前转换
    convertToModel?: (v: any) => any; // 值从 View → Model 前转换
};

export class DataBinder<T extends object> {
    private proxy: T;

    constructor(
        target: T,
        private bindings: Partial<Record<keyof T, BindingInfo>>,
    ) {
        this.proxy = this.createProxy(target);
        this.initView(); // 用初始值填充 View
        this.initEvents(); // 监听 View 变动，同步到 Model
    }

    /** 获取可操作的 Model 代理 */
    getModel(): T {
        return this.proxy;
    }

    /** 创建一个 Proxy，用于拦截 Model 改变并更新 View */
    private createProxy(target: T): T {
        const self = this;
        return new Proxy(target, {
            set: (target, prop, newValue, receiver) => {
                target[prop] = newValue;
                //@ts-ignore
                self.updateView(prop, newValue);
                return Reflect.set(target, prop, newValue, receiver);
            },
        });
    }

    /** 根据初始 Model 值更新一遍 View */
    private initView() {
        for (const key in this.bindings) {
            const info = this.bindings[key as keyof T]!;
            const raw = (this.proxy as any)[key];
            const v = info.convertToView ? info.convertToView(raw) : raw;
            (info.node as any)[info.viewProp] = v;
        }
    }

    /** 更新单个字段到对应 View */
    private updateView(prop: keyof T, value: any) {
        const info = this.bindings[prop];
        if (!info) return;
        const v = info.convertToView ? info.convertToView(value) : value;
        (info.node as any)[info.viewProp] = v;
    }

    /** 监听 View 事件，同步回 Model */
    private initEvents() {
        for (const key in this.bindings) {
            const info = this.bindings[key as keyof T]!;
            if (info.eventName) {
                (info.node as any).node.on(
                    info.eventName,
                    (event: any) => {
                        const raw = (info.node as any)[info.viewProp];
                        const v = info.convertToModel
                            ? info.convertToModel(raw)
                            : raw;
                        (this.proxy as any)[key] = v;
                    },
                    this,
                );
            }
        }
    }
}
